﻿package com.saftalk;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.geronimo.mail.util.Base64;

import com.google.appengine.labs.repackaged.org.json.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonIOException;
import com.opentok.api.API_Config;
import com.opentok.api.OpenTokSDK;
import com.saftalk.db.PMF;
import com.saftalk.db.entity.Family;
import com.saftalk.db.entity.User;
import com.saftalk.ui.UIDeletedUserResult;
import com.saftalk.ui.UIMessageResult;
import com.saftalk.ui.UIUserListResult;
import com.saftalk.ui.UIUserResult;
import com.saftalk.util.Emailer;


@SuppressWarnings("serial")
public class SaftalkServlet extends BaseServlet {

	private static Random rand = new Random(); 
	
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		resp.setContentType("text/plain; charset=UTF-8");

		GsonBuilder builder = new GsonBuilder();
		Gson gson = builder.create();

		try {
			if ("/logout".equals(req.getPathInfo())) {
				
				req.getSession().setAttribute("user", null);
				String url = req.getScheme()+ "://" + req.getServerName() +  ((req.getLocalPort() != 0 ) ? ":" + req.getLocalPort() : "") + "/";
				resp.sendRedirect(url);
				
			} else if ("/register".equals(req.getPathInfo())) {
	
				String name = req.getParameter("name");
				String password = req.getParameter("password");
				String email = req.getParameter("email");
				String phoneNumber = req.getParameter("phoneNumber");
				String seniorName = req.getParameter("seniorName");
	
				if (name == null || password == null || email == null || seniorName == null) {
					gson.toJson(new UIMessageResult(false, "Not enough parameters"),
							resp.getWriter());
					return;
				}
				
				long familyId = addFamily();
				
				User user = addUser(email, name, encryptPassword(password), null, phoneNumber, familyId, true, false);
				if (user == null) {
					gson.toJson(new UIUserResult(null), resp.getWriter());
					return;
				}
				String randomPass = getRandomPassword();
				addUser("senior" + familyId, seniorName, encryptPassword(randomPass), randomPass, null, familyId, false, true); 
				
				
				cache.remove(familyId);
				
				UIUserResult result = new UIUserResult(user);
				req.getSession().setAttribute("user", result);
				gson.toJson(result, resp.getWriter());
							
				
			} else if ("/getuser".equals(req.getPathInfo())) {
	
				String email = req.getParameter("email");
				String password = req.getParameter("password");
				UIUserResult result = getUser(email, password);
				if (result == null) {
					gson.toJson(new UIMessageResult(false, "Invalid email or pass"), resp.getWriter());
					return;
				}
				req.getSession().setAttribute("user", result);
				gson.toJson(result, resp.getWriter());
				
			} else if ("/user/info".equals(req.getPathInfo())){
				
				UIUserResult user = (UIUserResult) req.getSession().getAttribute(
						"user");
				
				if (user == null) {
					gson.toJson(new UIMessageResult(false, "Not logged in"),
							resp.getWriter());
					return;						
				}
				
				String userIdStr = req.getParameter("userId");
				if (userIdStr == null) {
					gson.toJson(new UIMessageResult(false, "Not enough params"),
							resp.getWriter());
					return;										
				}
				
				Long userId = Long.parseLong(userIdStr);
				if (!user.isAdmin()  && !user.getUserId().equals(userId)) {
					gson.toJson(new UIMessageResult(false, "Not authorized"),
							resp.getWriter());
					return;					
				}
				
				List<User> users = getUsersByFamilyId(user.getFamilyId());
				for (User u : users) {
					if (u.getId().equals(userId)) {
							gson.toJson(new UIUserResult(u),
									resp.getWriter());
							
						return;						
					}
				}
				
				gson.toJson(new UIMessageResult(false, "No such user"),
						resp.getWriter());
				return;						
				
				
			} else if ("/list".equals(req.getPathInfo())) {
				
				UIUserResult user = (UIUserResult) req.getSession().getAttribute(
						"user");
				
				if (user == null || !user.isAdmin()) {
					gson.toJson(new UIMessageResult(false, "Not logged in or not admin"),
							resp.getWriter());
					return;						
				}
				
				List<User> users = getUsersByFamilyId(user.getFamilyId());
				if (users == null || users.isEmpty()) {
					gson.toJson(new UIMessageResult(false, "Failed to fetch users"), resp.getWriter());
					return;				
				}
				List<UIUserResult> uiList;
				try {
					uiList = convertResults(users);
				} catch (Exception e) {
					gson.toJson(new UIMessageResult(false, "Failed to fetch users"), resp.getWriter());
					return;
				}
				gson.toJson(new UIUserListResult(uiList), resp.getWriter());
				
			} else if ("/sendAlert".equals(req.getPathInfo())) {
				
				UIUserResult user = (UIUserResult) req.getSession().getAttribute(
						"user");
				
				if (user == null) {
					gson.toJson(new UIMessageResult(false, "Not logged in"),
							resp.getWriter());
					return;						
				}
	
				boolean msgSuccess = false;
				if (user != null) {
					String seniorName=user.getName();
					msgSuccess = sendAlert(user.getFamilyId(),seniorName);
	
				}
				
				gson.toJson(new UIMessageResult(msgSuccess, "sms sent"),
						resp.getWriter());
	
			} else if ("/newchat".equals(req.getPathInfo())){
			JSONObject result=getNewChatSession();
			
			resp.getWriter().print(result.toString());
			} else {
				gson.toJson(
						new UIMessageResult(false, "Unknown command : "
								+ req.getPathInfo()), resp.getWriter());
			}
		} catch (NumberFormatException e){
			gson.toJson(new UIMessageResult(false, "Could not format number param"));
			return;
			
		} catch(NoSuchAlgorithmException e) {
			e.printStackTrace();
			gson.toJson(new UIMessageResult(false, "Critical error: no such algorithm"));
			return;
			
		} catch (JsonIOException e) {
			e.printStackTrace();
			resp.getWriter().println("{\"success\": false, \"message\":\"Failed to create JSON\" }");
			return;
		}
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		resp.setContentType("text/plain; charset=UTF-8");

		PersistenceManager pm = PMF.get().getPersistenceManager();
		GsonBuilder builder = new GsonBuilder();
		Gson gson = builder.create();
		

		try {
			if ("/user/edit".equals(req.getPathInfo())) {
				
				UIUserResult user = (UIUserResult) req.getSession().getAttribute(
						"user");
				
				if (user == null) {
					gson.toJson(new UIMessageResult(false, "Not logged in"),
							resp.getWriter());
					return;						
				}
				
				String userIdStr = req.getParameter("userId");
				String phoneNumber = req.getParameter("phoneNumber");
				String email = req.getParameter("email");
				String name = req.getParameter("name");
				String password = req.getParameter("password");
				boolean changePassword = req.getParameter("changePassword") != null;
				
				if (userIdStr == null || name == null || email == null || (changePassword && password == null)) {
					gson.toJson(new UIMessageResult(false, "Not enough params"),
							resp.getWriter());
					return;										
				}
				
				Long userId = Long.parseLong(userIdStr);
				if (!user.isAdmin()  && !user.getUserId().equals(userId)) {
					gson.toJson(new UIMessageResult(false, "Not authorized"),
							resp.getWriter());
					return;					
				}			
			
				
				Query q = pm.newQuery(User.class);
				User u;
				try {
					List<User> results;
					q.setFilter("id == idParam");
					q.declareParameters("Long idParam");
					results = (List<User>) q.execute(userId);
					
					if (results == null || results.isEmpty()) {
						gson.toJson(new UIMessageResult(false, "No such user"),
								resp.getWriter());
						return;											
					}
					u = results.get(0);
					u.setPhoneNumber(phoneNumber);
					u.setName(name);
					u.setEmail(email);
					if (changePassword) {
						if (u.isSenior()) {
							//FIXME: Fix this ugly workaround somehow!!!!
							u.setGeneratedPassword(password);
						} else {
							u.setPassword(encryptPassword(password));
							u.setGeneratedPassword(null);
						}
					}
					pm.makePersistent(u);
					
				} finally {
					q.closeAll();
					pm.close();
				}
				
				cache.remove(user.getFamilyId());
				gson.toJson(new UIUserResult(u), resp.getWriter());
				
			} else if ("/family/settings".equals(req.getPathInfo())) {
				
				UIUserResult user = (UIUserResult) req.getSession().getAttribute(
						"user");
				
				if (user == null || !user.isAdmin()) {
					gson.toJson(new UIMessageResult(false, "Not logged in"),
							resp.getWriter());
					return;						
				}
				
				String keyboardEnabledStr = req.getParameter("keyboardEnabled");
				String fontSizeStr = req.getParameter("fontSize");

				if (keyboardEnabledStr == null || fontSizeStr == null) {
					gson.toJson(new UIMessageResult(false, "Not enough params"),
							resp.getWriter());
					return;										
				}
				
				Boolean keyboardEnabled =  Boolean.parseBoolean(keyboardEnabledStr);
				Integer fontSize = Integer.parseInt(fontSizeStr);
				
				Query q = pm.newQuery(Family.class);
				
				try {
					
					List<Family> families;
					q.setFilter("id == idParam");
					q.declareParameters("Long idParam");
					families = (List<Family>) q.execute(user.getFamilyId());
					
					if (families == null || families.isEmpty()) {
						gson.toJson(new UIMessageResult(false, "No such family"),
								resp.getWriter());
						return;						
					}
					
					Family family = families.get(0);
					family.setFontSize(fontSize);
					family.setKeyboardEnabled(keyboardEnabled);
					
					pm.makePersistent(family);
					
				} finally {
					q.closeAll();
					pm.close();
				}
				
				gson.toJson(new UIMessageResult(true, "Settings updated"), resp.getWriter());
				
				
			} else if ("/user/remove".equals(req.getPathInfo())) {
				
				
				UIUserResult user = (UIUserResult) req.getSession().getAttribute(
						"user");
				
				if (user == null || !user.isAdmin()) {
					gson.toJson(new UIMessageResult(false, "Not logged in"),
							resp.getWriter());
					return;						
				}
				
				String userIdStr = req.getParameter("userId");
				Long userId = Long.parseLong(userIdStr);
				
				Query q = pm.newQuery(User.class);
				try {
					List<User> results;
					q.setFilter("id == idParam");
					q.declareParameters("Long idParam");
					results = (List<User>) q.execute(userId);
					
					if (results == null || results.isEmpty()) {
						gson.toJson(new UIMessageResult(false, "No such user"),
								resp.getWriter());
						return;											
					}
					User u = results.get(0);
					pm.deletePersistent(u);	
				} finally {
					pm.close();
				}
				
				cache.remove(user.getFamilyId());
				gson.toJson(new UIDeletedUserResult(userId),resp.getWriter());
				
			}  else if ("/adduser".equals(req.getPathInfo())) {
	
				UIUserResult  user = (UIUserResult) req.getSession().getAttribute("user");
				if (user == null || !user.isAdmin()) {
					gson.toJson(new UIMessageResult(false, "Not logged in"),
							resp.getWriter());
					return;				
				}
				String name = req.getParameter("name");
				String password = getRandomPassword();
				String email = req.getParameter("email");
				
				
				if (name == null || email == null ) {
					gson.toJson(new UIMessageResult(false, "Not enough parameters"),
							resp.getWriter());
					return;
				}
	
				
				long familyId = user.getFamilyId();
				User u = addUser(email, name, encryptPassword(password), password, null, familyId, false, false);
				
	
				Emailer.send(name, email, password);
				
				cache.remove(familyId);
				gson.toJson(new UIUserResult(u),
							resp.getWriter());
	
			} 
		} catch (NumberFormatException e){
			gson.toJson(new UIMessageResult(false, "Could not format number param"));
			return;
			
		} catch(NoSuchAlgorithmException e) {
			e.printStackTrace();
			gson.toJson(new UIMessageResult(false, "Critical error: no such algorithm"));
			return;
			
		} catch (JsonIOException e) {
			e.printStackTrace();
			resp.getWriter().println("{\"success\": false, \"message\":\"Failed to create JSON\" }");
			return;
		}
	}
	
	private String getRandomPassword() {
		
		Integer pass = rand.nextInt(10000);
		return String.format("%04d", pass);
	}

	private JSONObject getNewChatSession(){
		JSONObject retVal= new JSONObject();
		
		OpenTokSDK sdk = new OpenTokSDK(API_Config.API_KEY,API_Config.API_SECRET);

		//Generate a token
		try {
			String sessionId = sdk.create_session().getSessionId();
			retVal.put("sessionId", sessionId); 
			retVal.put("token",sdk.generate_token(sessionId));
			retVal.put("success",true);
			
		} catch (Exception e) {
		
			e.printStackTrace();
		}
		return retVal;
	
	}
	private List<User> getUsersByFamilyId(long familyId) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery(User.class);
		List<User> results;
		try {
			q.setFilter("familyId == familyIdParam");
			q.declareParameters("long familyIdParam");
			results = (List<User>) q.execute(familyId);
		} finally {
			q.closeAll();
		}
		return results;
	}

	private UIUserResult getUser(String email, String password)
			throws IOException, NoSuchAlgorithmException {

		if (email == null || password == null) {
			return new UIUserResult(null);
		}
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query q = pm.newQuery(User.class);
		Query q2 = pm.newQuery(Family.class);
		try {
			List<User> results;
			q.setFilter("email == emailParam");
			q.declareParameters("String emailParam");
			results = (List<User>) q.execute(email);
			
			if (results.isEmpty()) {
				return null;
			}
			User user = results.get(0);
			
			List<Family> families;
			q2.setFilter("id == idParam");
			q2.declareParameters("Long idParam");
			families = (List<Family>) q2.execute(user.getFamilyId());
			
			if (families.isEmpty()) {
				return null;
			}
			Family family = families.get(0);
			
			String encryptedPass = encryptPassword(password);
			if (user.isAdmin()) {
				
				if (!encryptedPass.equals(user.getPassword())) {
					return null;
				} 				
			} else {
				if (!encryptedPass.equals(user.getPassword()) && !password.equals(user.getGeneratedPassword())) {
					return null;
				} 
			}
			
			
			return new UIUserResult(results.get(0), family);
		} finally {
			q.closeAll();
		}

	}

	private User addUser(String email, String name, String password, String generatedPassword, String phoneNumber,
			long familyId, boolean isAdmin, boolean isSenior) {

		PersistenceManager pm = PMF.get().getPersistenceManager();
		if (name == null || password == null) {
			return null;
		}

		User u = new User(email, name, password, generatedPassword, familyId);		 
		u.setReceiveSms(true);
		u.setPhoneNumber(phoneNumber);
		u.setAdmin(isAdmin);
		u.setSenior(isSenior);
		Query q = pm.newQuery(User.class);
		try {
			List<User> users;
			q.setFilter("email == emailParam");
			q.declareParameters("String emailParam");
			users = (List<User>) q.execute(u.getEmail());
			
			if (users != null && !users.isEmpty()) {
				return null;
			}
			pm.makePersistent(u);
			
		} finally {
			q.closeAll();
			pm.close();
		}
		
		return u;
	}

	private long addFamily() {

		PersistenceManager pm = PMF.get().getPersistenceManager();

		Family f = new Family();

		try {
			pm.makePersistent(f);
		} finally {
			pm.close();
		}
		return f.getId();
	}

	private String encryptPassword(String password)
			throws NoSuchAlgorithmException {
		MessageDigest md = MessageDigest.getInstance("MD5");
		byte[] pass = md.digest(password.getBytes());
		return new String(Base64.encode(pass));

	}
	
	private String preparePhoneNumberForSms(String phoneNumber){
		String tmpNumber=phoneNumber;
		if (tmpNumber == null || tmpNumber.length()<5){
			return "0";
		}
		if (tmpNumber.subSequence(0, 1).equals("0")){
			tmpNumber="972"+tmpNumber.subSequence(1, tmpNumber.length());
		}
		return tmpNumber;
	}

	private boolean sendAlert(long familyId, String seniorName) {
		List<User> users = getUsersByFamilyId(familyId);
		users.isEmpty();
		String NEXMO_API_KEY = "e74df76d";
		String NEXMO_API_SECRET = "6261d32e";
		String NEXMO_HTTP = "http://rest.nexmo.com/sms/json";
		/*NEXMO_HTTP ="http://www.walla.co.il/";*/
		String urlStr;
		String phoneNumber = "";
		String userName = "";
		String smsText = "";

		if (!users.isEmpty()) {
			for (User user : users) {
				userName=user.getName();
				phoneNumber=preparePhoneNumberForSms(user.getPhoneNumber());
				 
				smsText = userName + " שלום, זו היא הודעה ממערכת Saftalk."
						+ " כפתור הודעה דחופה נלחץ על ידי " + seniorName
						+ " אנו ממליצים ליצור קשר בהקדם.";
				urlStr = NEXMO_HTTP + "?type=unicode";
				String myQuery;
				try {
					myQuery = "&api_key=" + NEXMO_API_KEY + "&api_secret="
							+ NEXMO_API_SECRET + "&from=SafTalk&to=" + phoneNumber
							+ "&text=" + URLEncoder.encode(smsText, "UTF8");
					urlStr = urlStr + myQuery;
					if (phoneNumber != null && !phoneNumber.isEmpty() && user.isReceiveSms() ){
					 try {
					
					 String resp=httpGet(urlStr); //disable sms until production
					 } catch (IOException e) {
					 return false;
					 }
					}
					

				} catch (UnsupportedEncodingException e2) {
					return false;
				}
			}
		} else {
			// Handle "no results" case
		}

		

		
		return true;
	}

	public static String httpGet(String urlStr) throws IOException {

		URL url = new URL(urlStr);
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();

		if (conn.getResponseCode() != 200) {
			throw new IOException(conn.getResponseMessage());
		}

		// Buffer the result into a string
		BufferedReader rd = new BufferedReader(new InputStreamReader(
				conn.getInputStream()));
		StringBuilder sb = new StringBuilder();
		String line;
		while ((line = rd.readLine()) != null) {
			sb.append(line);
		}
		rd.close();

		conn.disconnect();
		return sb.toString();
	}
	
	private List<UIUserResult> convertResults(List<User> users) throws Exception {
		
		List<UIUserResult> uiList = new ArrayList<UIUserResult>();
		for (User user: users) {
			uiList.add(new UIUserResult(user));
		}
		return uiList;
	}
}
